<template>
  <div class="bluetooth">
    <div v-if="loading" class="overlay">
      <div class="loading-box">🔄 {{ loadingMessage }}</div>
    </div>
    <div class="passnumber" v-if="qualityCount > 0">
      <p>合格上传</p>
      <p>{{ qualityCount }}</p>
    </div>
    <div class="handle-box">
      <div class="btn-box">
        <button @click="addDevice">添加蓝牙设备</button>
        <button style="background: steelblue" @click="sensorOpen">
          校准传感器
        </button>
        <button style="background: sienna" @click="rockerOpen">校准摇杆</button>
        <button
          :style="{ background: isMotor ? '#191970' : '#ff4500' }"
          @click="motorOpen"
        >
          {{ isMotor ? "关闭" : "开启" }}马达震动
        </button>
        <button style="background: #dc143c" @click="writeOpen">写入信息</button>
      </div>
      <div class="input-box">
        <el-input
          placeholder="请输入质检员账号"
          prefix-icon="el-icon-user-solid"
          v-model="qualityTestingAccount"
          @input="saveQualityTestingAccount"
        >
        </el-input>
        <el-input
          placeholder="请输入质检码（密码）"
          prefix-icon="el-icon-mouse"
          v-model="qualityTestingCode"
          @input="saveQualityTestingCode"
        >
        </el-input>
        <el-button type="primary" class="add-btn" @click="sumbitQualified"
          >合格上传</el-button
        >
      </div>
      <div class="operate-box">
        <div class="seleted" @click="changeAllCheck">
          <p>
            <img
              :src="
                isAllCheck
                  ? require('../assets/checked.png')
                  : require('../assets/check.png')
              "
              alt=""
            />
          </p>
          全选
        </div>
        <el-button class="reset-btn" @click="clearAll">一键清空</el-button>
      </div>
    </div>
    <el-divider>温馨提示：设备连接后，请先校准摇杆再做摇杆检测！</el-divider>
    <div class="device-block">
      <el-card
        shadow="hover"
        class="box-card"
        v-for="(d, i) in devices"
        :key="i"
        :style="{
          background: d.passed ? 'rgba(99, 255, 177, 0.4)' : '#fff',
        }"
        :class="{
          'blink-error': d.isSensor === 'fail' || d.isRocker === 'fail',
        }"
      >
        <div
          slot="header"
          class="clearfix"
          :style="{
            background: d.connected ? '#fff' : 'rgba(	255,182,193,0.6)',
          }"
        >
          <div class="title">
            <h2>{{ d.device.name || "未知设备" }}</h2>
            <p>{{ d.connected ? "🟢" : "🔴" }}</p>
          </div>
          <div class="battery-box">
            <div class="battery-shell">
              <div
                class="battery-level"
                :style="{
                  width: d.batteryPercent + '%',
                  backgroundColor: d.batteryColor,
                }"
              ></div>
            </div>
            <p class="battery-text">{{ d.batteryPercent }}</p>
          </div>
        </div>
        <div class="key-box">
          <div class="top">
            <div
              :style="{
                background: d.keyTestStatus.L ? '#32CD32' : '#333333',
              }"
            >
              L
            </div>
            <div
              :style="{
                background: d.keyTestStatus.ZL ? '#32CD32' : '#333333',
              }"
            >
              ZL
            </div>
            <div
              :style="{
                background: d.keyTestStatus.ZR ? '#32CD32' : '#333333',
              }"
            >
              ZR
            </div>
            <div
              :style="{
                background: d.keyTestStatus.R ? '#32CD32' : '#333333',
              }"
            >
              R
            </div>
          </div>
          <div class="center">
            <div class="joystick-container">
              <div class="joystick-base">
                <div
                  class="joystick-stick"
                  :style="{
                    transform: `translate(${d.direction.x}px, ${d.direction.y}px)`,
                    background:
                      d.gyroBtn || d.rockerChecked ? '#27FF2C' : '#ffffff',
                  }"
                ></div>
              </div>
            </div>
            <div class="button">
              <div
                class="cicle-button"
                :style="{
                  background: d.keyTestStatus.X ? '#32CD32' : '#333333',
                }"
              >
                X
              </div>
              <div class="cicle-box">
                <div
                  class="cicle-button"
                  :style="{
                    background: d.keyTestStatus.Y ? '#32CD32' : '#333333',
                  }"
                >
                  Y
                </div>
                <div
                  class="cicle-button"
                  :style="{
                    background: d.keyTestStatus.A ? '#32CD32' : '#333333',
                  }"
                >
                  A
                </div>
              </div>
              <div
                class="cicle-button"
                :style="{
                  background: d.keyTestStatus.B ? '#32CD32' : '#333333',
                }"
              >
                B
              </div>
            </div>
          </div>
          <el-divider></el-divider>
          <div class="gyroscope">
            <div>
              <div v-if="d.isSensor === 'loading'" class="loading-dots">
                <span class="dot"></span>
                <span class="dot"></span>
                <span class="dot"></span>
                <p>九轴等待校准</p>
              </div>
              <div v-else-if="d.isSensor === 'success'" class="check-success">
                <div class="check-icon">✔</div>
                <p>九轴校准成功</p>
              </div>
              <div v-else class="check-fail">
                <div class="check-icon">⨉</div>
                <p>九轴校准失败</p>
              </div>
            </div>
            <div>
              <div v-if="d.isRocker === 'loading'" class="loading-dots">
                <span class="dot"></span>
                <span class="dot"></span>
                <span class="dot"></span>
                <p>摇杆等待校准</p>
              </div>
              <div v-else-if="d.isRocker === 'success'" class="check-success">
                <div class="check-icon">✔</div>
                <p>摇杆校准成功</p>
              </div>
              <div v-else class="check-fail">
                <div class="check-icon">⨉</div>
                <p>摇杆校准失败</p>
              </div>
            </div>
            <div>
              <div v-if="!d.keyChecked" class="loading-dots">
                <span class="dot"></span>
                <span class="dot"></span>
                <span class="dot"></span>
                <p>按键检测中</p>
              </div>
              <div v-else class="check-success">
                <div class="check-icon">✔</div>
                <p>按键全部正常</p>
              </div>
            </div>
            <div>
              <div v-if="!d.rockerChecked" class="loading-dots">
                <span class="dot"></span>
                <span class="dot"></span>
                <span class="dot"></span>
                <p>摇杆检测中</p>
              </div>
              <div v-else class="check-success">
                <div class="check-icon">✔</div>
                <p>摇杆检测正常</p>
              </div>
            </div>
          </div>
        </div>
        <el-divider></el-divider>
        <div class="button-box">
          <p v-if="d.connected" @click="changeCheck(i)" style="color: #ff4500">
            <img
              :src="
                d.isSeleted
                  ? require('../assets/checked.png')
                  : require('../assets/check.png')
              "
              alt=""
            />
            选择
          </p>
          <p
            class="reconnect"
            v-if="!d.connected"
            @click="reconnectDevice(d)"
            style="color: #454545; border-color: #454545"
          >
            <i class="el-icon-cpu"></i>
            重连
          </p>
          <p
            class="remove-box"
            @click="delDevice(i)"
            style="color: red; border-color: red"
          >
            <i class="el-icon-remove"></i>
            移除
          </p>
        </div>
      </el-card>
    </div>
    <!-- 校准传感器的弹窗 -->
    <el-dialog
      :visible.sync="sensorVisible"
      width="30%"
      :before-close="sensorClose"
      :show-close="false"
    >
      <div slot="title" class="title">
        <p>提示</p>
        <span @click="sensorClose"><i class="el-icon-close"></i></span>
      </div>
      <div class="sensor-box">请将手柄静止放置在桌面上</div>
      <span slot="footer" class="dialog-footer">
        <el-button class="cancel-btn" @click="sensorClose">取 消</el-button>
        <el-button type="primary" class="enter-btn" @click="sumbitSensor()"
          >确 定</el-button
        >
      </span>
    </el-dialog>
    <!-- 校准摇杆的弹窗 -->
    <el-dialog
      :visible.sync="rockerVisible"
      width="30%"
      :before-close="rockerClose"
      :show-close="false"
    >
      <div slot="title" class="title">
        <p>提示</p>
        <span @click="rockerClose"><i class="el-icon-close"></i></span>
      </div>
      <div class="rocker-box">
        <p>1.请将手柄处于一个完全水平的状态</p>
        <p>2.待灯光亮起，左右上下摇动一下摇杆</p>
        <p>3.按B结束校验</p>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button class="cancel-btn" @click="rockerClose">取 消</el-button>
        <el-button type="primary" class="enter-btn" @click="sumbitRocker()"
          >确 定</el-button
        >
      </span>
    </el-dialog>
    <!-- 测试马达的弹窗 -->
    <el-dialog
      :visible.sync="motorVisible"
      width="30%"
      :before-close="motorClose"
      :show-close="false"
    >
      <div slot="title" class="title">
        <p>调节马达</p>
        <span @click="motorClose"><i class="el-icon-close"></i></span>
      </div>
      <div class="motor-box">
        <div>
          <span class="demonstration">设置震荡马达的力度（pwm）</span>
          <div>
            <el-slider v-model="power"></el-slider>
            <p>{{ power }}</p>
          </div>
        </div>
        <div>
          <span class="demonstration">设置震荡马达的时间（ms）</span>
          <div>
            <el-slider v-model="time" :max="255"></el-slider>
            <p>{{ time }}</p>
          </div>
        </div>
      </div>
      <span slot="footer" class="dialog-footer">
        <el-button class="cancel-btn" @click="motorClose">取 消</el-button>
        <el-button type="primary" class="enter-btn" @click="sumbitMotor()"
          >确 定</el-button
        >
      </span>
    </el-dialog>
    <!-- 写入信息弹框 -->
    <el-dialog
      :visible.sync="writeVisible"
      width="90%"
      :before-close="writeClose"
      destroy-on-close
      :show-close="false"
    >
      <div slot="title" class="title">
        <p>查看和修改设备信息</p>
        <span @click="writeClose"><i class="el-icon-close"></i></span>
      </div>
      <div>
        <el-table :data="devices" border style="width: 100%">
          <el-table-column prop="device.name" label="设备名称" width="120" />
          <el-table-column label="设备型号">
            <template slot-scope="{ row }">
              <el-input v-model="row.deviceMsg[1]" placeholder="设备型号" />
            </template>
          </el-table-column>
          <el-table-column label="开关机次数">
            <template slot-scope="{ row }">
              <el-input v-model="row.deviceMsg[2]" placeholder="开关机次数" />
            </template>
          </el-table-column>
          <el-table-column label="充电循环次数">
            <template slot-scope="{ row }">
              <el-input v-model="row.deviceMsg[3]" placeholder="充电循环次数" />
            </template>
          </el-table-column>
          <el-table-column label="设备颜色">
            <template slot-scope="{ row }">
              <el-select v-model="row.deviceMsg[4]" placeholder="选择设备颜色">
                <el-option
                  v-for="item in colorOptions"
                  :key="item.hex"
                  :label="`${item.name_zh} (${item.name_en})`"
                  :value="item.name_en"
                >
                  <span :style="{ color: item.hex, marginRight: '10px' }"
                    >■</span
                  >
                  {{ item.name_zh }} ({{ item.name_en }})
                </el-option>
              </el-select>
            </template>
          </el-table-column>
          <el-table-column label="销售地区">
            <template slot-scope="{ row }">
              <el-select v-model="row.deviceMsg[5]" placeholder="选择销售地区">
                <el-option
                  v-for="item in countryOptions"
                  :key="item.code"
                  :label="`${item.zh} (${item.en})`"
                  :value="item.code"
                >
                  {{ item.zh }} ({{ item.en }})
                </el-option>
              </el-select>
            </template>
          </el-table-column>
          <el-table-column label="蓝牙MAC地址">
            <template slot-scope="{ row }">
              <el-input v-model="row.deviceMsg[6]" placeholder="蓝牙MAC地址" />
            </template>
          </el-table-column>
          <el-table-column label="机序列号">
            <template slot-scope="{ row }">
              <el-input v-model="row.deviceMsg[7]" placeholder="机序列号" />
            </template>
          </el-table-column>
          <el-table-column label="电池序列号">
            <template slot-scope="{ row }">
              <el-input v-model="row.deviceMsg[8]" placeholder="电池序列号" />
            </template>
          </el-table-column>
          <el-table-column label="生产日期">
            <template slot-scope="{ row }">
              <el-input v-model="row.deviceMsg[9]" placeholder="生产日期" />
            </template>
          </el-table-column>
          <el-table-column label="固件版本号">
            <template slot-scope="{ row }">
              <el-input v-model="row.deviceMsg[10]" placeholder="固件版本号" />
            </template>
          </el-table-column>
          <el-table-column label="手柄密钥">
            <template slot-scope="{ row }">
              <el-select v-model="row.secret" placeholder="选择手柄密钥">
                <el-option
                  v-for="item in secretOptions"
                  :key="item.key"
                  :label="item.label"
                  :value="item.key"
                >
                </el-option>
              </el-select>
            </template>
          </el-table-column>
        </el-table>
        <div
          slot="footer"
          class="dialog-footer"
          style="margin-top: 20px; text-align: right"
        >
          <el-button class="cancel-btn" @click="writeClose">取 消</el-button>
          <el-button
            type="primary"
            class="enter-btn"
            @click="sumbitWrite()"
            :loading="writeLoading"
            >确 定</el-button
          >
        </div>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import tools from "@/utils/tools.js";
import { addCheckDevice, getQualityCount } from "@/api";
import colorOptions from "@/assets/color.json";
import countryOptions from "@/assets/country.json";
export default {
  data() {
    return {
      qualityCount: 0,
      colorOptions,
      countryOptions,
      secretOptions: [
        {
          key: "5713F7FDFE",
          label: "HYKJ",
        },
        {
          key: "295CF37FBA",
          label: "NBLR",
        },
        {
          key: "365B2EC460",
          label: "宏屹科技",
        },
        {
          key: "B3B7B6C0F6",
          label: "宁波力柔",
        },
        {
          key: "EC21E46579",
          label: "杭州宏屹智能科技合伙企业（有限合伙）",
        },
      ],
      serviceUUID: "00000000-0000-1000-8000-00805f9b34fb",
      notifyUUID: "0000ffe2-0000-1000-8000-00805f9b34fb",
      isAllCheck: false,
      devices: [],
      // 传感器
      sensorVisible: false,
      // 摇杆
      rockerVisible: false,
      // 马达
      motorVisible: false,
      isMotor: false,
      // 信息
      writeVisible: false,
      power: 0,
      time: 0,
      sendHex: "", // 用户输入的 hex 字符串
      sendTargetIndex: 0, // 发送目标设备索引
      loading: false,
      loadingMessage: "",
      // 选中的数量
      selectNumber: 0,
      nowNumber: 0,
      qualityTestingAccount: "", //质检员账号
      qualityTestingCode: "", //质检码
      uploading: false,
      writeLoading: false,
    };
  },
  created() {
    this.qualityTestingAccount = localStorage.getItem("QualityTestingAccount");
    this.qualityTestingCode = localStorage.getItem("QualityTestingCode");
  },
  async mounted() {
    this.restoreRememberedDevices();
    window.addEventListener("beforeunload", this.disconnectAll);
  },
  beforeDestroy() {
    this.disconnectAll();
    window.removeEventListener("beforeunload", this.disconnectAll);
  },

  methods: {
    async addDevice() {
      if (this.devices.length >= 20) {
        this.$message.warning("仅支持一次性检测20个设备");
        return;
      }
      try {
        const device = await navigator.bluetooth.requestDevice({
          filters: [{ namePrefix: "L05" }],
          optionalServices: [this.serviceUUID],
        });

        await this.connectDevice(device); // 分离逻辑
        this.saveRememberedDevice(device.id);
      } catch (error) {
        console.warn("用户取消或添加失败", error);
      }
    },

    async connectDevice(device) {
      this.loading = true;
      this.loadingMessage = `正在连接设备 ${device.name}...`;
      try {
        const server = await device.gatt.connect();
        const service = await server.getPrimaryService(this.serviceUUID);
        const notifyChar = await service.getCharacteristic(this.notifyUUID);

        await notifyChar.startNotifications();

        const deviceObj = await this.buildDeviceObject(
          device,
          server,
          notifyChar
        );
        this.registerNotify(deviceObj);
        this.devices.push(deviceObj);
        this.saveRememberedDevice(device.id);

        device.addEventListener("gattserverdisconnected", () => {
          deviceObj.connected = false;
          // console.warn(`[${device.name}] 连接断开，尝试重连...`);
          // this.retryReconnect(deviceObj, 3); // 尝试重连最多 3 次
          if (!deviceObj.manuallyDisconnect) {
            console.warn(`[${device.name}] 连接断开，尝试重连...`);
            this.retryReconnect(deviceObj, 3);
          } else {
            console.log(`[${device.name}] 手动断开，不执行重连`);
          }
        });

        console.log("✅ 成功连接：", device.name);
        console.log(deviceObj, "deviceObj");
        // this.loading = false;
      } catch (err) {
        console.error("❌ 连接失败：", err);
        this.$message.error(`❌ ${device.name}连接失败，请重新尝试连接。`);
        if (device.gatt?.connected) {
          device.gatt.disconnect();
          console.log("🔌 连接失败后强制断开");
        }
        // this.loading = false;
      } finally {
        // ✅ 无论连接成功或失败，此处再关闭 loading
        this.loading = false;
      }
    },
    // 自动重新连接三次
    async retryReconnect(deviceObj, maxRetry = 3) {
      let attempt = 0;

      const delay = (ms) => new Promise((res) => setTimeout(res, ms));

      while (attempt < maxRetry) {
        attempt++;
        console.log(
          `🔁 第 ${attempt} 次尝试重连 [${deviceObj.device.name}]...`
        );

        try {
          const server = await deviceObj.device.gatt.connect();
          const service = await server.getPrimaryService(this.serviceUUID);
          const characteristic = await service.getCharacteristic(
            this.notifyUUID
          );

          await characteristic.startNotifications();
          characteristic.addEventListener(
            "characteristicvaluechanged",
            (event) => this.handleNotify(event, deviceObj)
          );

          // 恢复状态
          deviceObj.connected = true;
          deviceObj.server = server;
          deviceObj.characteristic = characteristic;
          deviceObj.isReconnection = false;
          console.log(`✅ 重新连接成功: [${deviceObj.device.name}]`);
          return;
        } catch (error) {
          console.warn(`❌ 第 ${attempt} 次重连失败:`, error);
          await delay(1500); // 等待 1.5 秒再重试
        }
      }
      deviceObj.isReconnection = true;
      console.error(
        `❌ 无法重新连接设备 [${deviceObj.device.name}]，请手动重连`
      );
    },
    // 手动重新连接
    async reconnectDevice(deviceObj) {
      try {
        if (!deviceObj.device.gatt.connected) {
          console.log(`🔁 正在尝试手动重连 [${deviceObj.device.name}]...`);
          const server = await deviceObj.device.gatt.connect();
          const service = await server.getPrimaryService(this.serviceUUID);
          const characteristic = await service.getCharacteristic(
            this.notifyUUID
          );

          await characteristic.startNotifications();
          characteristic.addEventListener(
            "characteristicvaluechanged",
            (event) => this.handleNotify(event, deviceObj)
          );

          // 更新 deviceObj 状态
          deviceObj.connected = true;
          deviceObj.server = server;
          deviceObj.characteristic = characteristic;

          console.log(`✅ 手动重连成功: ${deviceObj.device.name}`);
        } else {
          console.log(`✅ 设备已连接: ${deviceObj.device.name}`);
        }
      } catch (error) {
        console.error(`❌ 手动重连失败: ${deviceObj.device.name}`, error);
        alert(`设备 [${deviceObj.device.name}] 连接失败，请确认设备开启`);
      }
    },
    async restoreRememberedDevices() {
      const stored = JSON.parse(
        localStorage.getItem("rememberedBluetoothDevices") || "[]"
      );
      if (navigator.bluetooth && navigator.bluetooth.getDevices) {
        const remembered = await navigator.bluetooth.getDevices();
        for (const d of remembered) {
          if (stored.includes(d.id)) {
            try {
              await this.connectDevice(d);
            } catch (err) {
              console.warn("恢复连接失败：", err);
            }
          }
        }
      }
    },

    buildDeviceObject(device, server, characteristic) {
      return {
        device,
        server,
        characteristic,
        hexCache: "",
        isRocker: "loading",
        isSensor: "loading",
        isReconnection: false,
        manuallyDisconnect: false,
        connected: true,
        messages: [],
        keyList: [],
        gyroBtn: false,
        direction: { x: 0, y: 0 },
        isSeleted: false,
        secret: "",
        passed: false,
        deviceMsg: {
          1: "",
          2: "",
          3: "",
          4: "",
          5: "",
          6: "",
          7: "",
          8: "",
          9: "",
          10: "",
        },
        keyTestStatus: {
          X: false,
          Y: false,
          B: false,
          A: false,
          L: false,
          ZL: false,
          ZR: false,
          R: false,
        },
        rockerTestStatus: {
          up: false,
          down: false,
          left: false,
          right: false,
          press: false,
        },
        batteryPercent: 0,
        backgroundColor: "#fff",
        rockerChecked: false,
        keyChecked: false,
      };
    },

    registerNotify(deviceObj) {
      deviceObj.characteristic.addEventListener(
        "characteristicvaluechanged",
        (event) => {
          this.handleNotify(event, deviceObj);
        }
      );
    },

    handleNotify(event, deviceObj) {
      const value = event.target.value;
      const hexStr = Array.from(new Uint8Array(value.buffer))
        .map((b) => b.toString(16).padStart(2, "0"))
        .join("")
        .toLowerCase();

      if (hexStr.includes("a506aa7788")) {
        // alert(`[${deviceObj.device.name}] 摇杆校验成功`);
        deviceObj.isRocker = "success";
        return;
      }
      if (hexStr.includes("a506ae7788")) {
        // alert(`[${deviceObj.device.name}] 摇杆校验失败`);
        deviceObj.isRocker = "fail";
        return;
      }
      if (hexStr.includes("a506aa5566")) {
        // alert(`[${deviceObj.device.name}] 6轴校准成功`);
        deviceObj.isSensor = "success";
        return;
      }
      if (hexStr.includes("a506ae5566")) {
        // alert(`[${deviceObj.device.name}] 6轴校准失败`);
        deviceObj.isSensor = "fail";
        return;
      }
      if (hexStr.startsWith("a5") && hexStr.slice(4, 6) === "a1") {
        const bytes = [];
        for (let i = 0; i < hexStr.length; i += 2) {
          bytes.push(parseInt(hexStr.substr(i, 2), 16));
        }

        // 简单校验长度
        if (bytes.length >= 6) {
          const len = bytes[1];
          if (len === bytes.length) {
            const cmd = bytes[3];
            const asciiBytes = bytes.slice(4, len - 1);
            let str = "";
            asciiBytes.forEach((b) => {
              str += String.fromCharCode(b);
            });
            // this.processData(Number(cmd), str);
            if (Number(cmd) === 5) {
              const country = str.split("_");
              if (country.length > 1) {
                deviceObj.secret = country[1];
              } else {
                deviceObj.secret = "5713F7FDFE";
              }
              deviceObj.deviceMsg[Number(cmd)] =
                country[0] === "China" ? "CN" : country[0];
            } else {
              deviceObj.deviceMsg[Number(cmd)] = str;
            }
            return; // 拦截，不执行后续逻辑
            // }
          }
        }

        return; // 阻止后续原有代码执行
      }
      deviceObj.hexCache += hexStr;

      while (deviceObj.hexCache.length >= 6) {
        let found = false;
        for (let i = 0; i <= deviceObj.hexCache.length - 6; i += 2) {
          if (deviceObj.hexCache.slice(i, i + 2) === "a5") {
            const len = parseInt(deviceObj.hexCache.slice(i + 2, i + 4), 16);
            const frameLen = len * 2;
            if (deviceObj.hexCache.length - i < frameLen) break;

            const frameHex = deviceObj.hexCache.slice(i, i + frameLen);
            let sum = 0;
            for (let j = 0; j < frameLen - 2; j += 2) {
              sum += parseInt(frameHex.slice(j, j + 2), 16);
            }
            sum = sum & 0xff;
            const checkSumHex = frameHex.slice(frameLen - 2);
            const checkSum = parseInt(checkSumHex, 16);

            if (sum === checkSum) {
              const payload = frameHex.slice(6, frameLen - 2);
              this.processData(payload, deviceObj);
              deviceObj.hexCache = deviceObj.hexCache.slice(i + frameLen);
              found = true;
              break;
            } else {
              deviceObj.hexCache = deviceObj.hexCache.slice(i + 2);
              found = true;
              break;
            }
          }
        }
        if (!found) break;
      }
    },

    processData(payloadHex, deviceObj) {
      //按键
      this.processKey(payloadHex.slice(0, 2), deviceObj);
      // 处理摇杆数据
      this.processRocker(
        payloadHex.slice(2, 10),
        payloadHex.slice(64, 65),
        deviceObj
      );
      // 电量
      this.processBattery(payloadHex.slice(62, 64), deviceObj);

      // 判断检验是否合格
      this.checkDevicePassed(deviceObj);
    },
    checkDevicePassed(deviceObj) {
      if (deviceObj.passed) return;
      if (
        deviceObj.rockerChecked &&
        deviceObj.keyChecked &&
        deviceObj.isRocker === "success" &&
        deviceObj.isSensor === "success"
      ) {
        // if (!deviceObj.passed) {
        deviceObj.passed = true; // 标记为合格
        // }
      }
    },
    processKey(value, deviceObj) {
      const btnValue = tools.hex2int(value);
      const buttons = {
        X: 1,
        Y: 2,
        B: 4,
        A: 8,
        L: 128,
        ZL: 64,
        ZR: 16,
        R: 32,
      };
      const pressed = [];
      for (let key in buttons) {
        if ((btnValue & buttons[key]) !== 0) {
          pressed.push(key);
          // 更新测试状态
          if (deviceObj.keyTestStatus) {
            deviceObj.keyTestStatus[key] = true;
          }
        }
      }
      deviceObj.keyList = pressed;
      this.checkKeyTestComplete(deviceObj);
    },
    // isKeyTestComplete(deviceObj) {
    //   return (
    //     deviceObj.keyTestStatus &&
    //     Object.values(deviceObj.keyTestStatus).every((v) => v === true)
    //   );
    // },
    checkKeyTestComplete(deviceObj) {
      if (deviceObj.keyChecked) return;
      const allTested = Object.values(deviceObj.keyTestStatus).every((v) => v);
      if (allTested) {
        deviceObj.keyChecked = true;
      }
    },
    // 处理摇杆
    processRocker(value, key, deviceObj) {
      const x = this.parseLittleEndian16(value.slice(0, 4)); // 归一化后的 [-1, 1]
      const y = this.parseLittleEndian16(value.slice(4, 8));
      deviceObj.direction.x = x * 20;
      deviceObj.direction.y = -y * 20;
      deviceObj.gyroBtn = tools.hex2int(key) === 8 ? true : false;
      const threshold = 0.3;
      if (deviceObj.rockerTestStatus) {
        if (y < -threshold) deviceObj.rockerTestStatus.up = true;
        if (y > threshold) deviceObj.rockerTestStatus.down = true;
        if (x < -threshold) deviceObj.rockerTestStatus.left = true;
        if (x > threshold) deviceObj.rockerTestStatus.right = true;
        deviceObj.rockerTestStatus.press = tools.hex2int(key) === 8;
      }
      this.checkRockerComplete(deviceObj);
    },

    // isCheckRockerComplete(deviceObj) {
    //   return (
    //     deviceObj.rockerTestStatus &&
    //     Object.values(deviceObj.rockerTestStatus).every((v) => v === true)
    //   );
    // },
    checkRockerComplete(deviceObj) {
      if (deviceObj.isRocker !== "success") return;
      if (deviceObj.rockerChecked) return;
      const allTested = Object.values(deviceObj.rockerTestStatus).every(
        (v) => v
      );
      if (allTested) {
        deviceObj.rockerChecked = true;
      }
    },
    // 处理电量
    processBattery(value, deviceObj) {
      const battery = tools.hex2int(value);
      deviceObj.batteryPercent = battery;
      deviceObj.batteryColor =
        battery >= 60 ? "#46a149" : battery >= 20 ? "#f0b606" : "#F44336";
    },
    parseLittleEndian16(hexStr) {
      if (hexStr.length !== 4) throw new Error("必须是4位hex字符串");
      const reordered = hexStr.slice(2, 4) + hexStr.slice(0, 2);
      let value = parseInt(reordered, 16);
      if (value > 0x7fff) value -= 0x10000; // 有符号转换
      const normalized = Math.max(-1, Math.min(1, value / 32767));
      return Number(normalized.toFixed(4));
    },
    saveRememberedDevice(id) {
      const stored = JSON.parse(
        localStorage.getItem("rememberedBluetoothDevices") || "[]"
      );
      if (!stored.includes(id)) {
        stored.push(id);
        localStorage.setItem(
          "rememberedBluetoothDevices",
          JSON.stringify(stored)
        );
      }
    },
    disconnectAll() {
      this.devices.forEach((d) => {
        if (d.device?.gatt?.connected) {
          d.device.gatt.disconnect();
        }
      });
    },
    // 删除连接蓝牙设备
    delDevice(index) {
      this.$confirm("即将为您移除这个设备", "提示", {
        confirmButtonText: "确定",
        showCancelButton: false,
        type: "warning",
        showClose: false,
      })
        .then(async () => {
          if (index !== -1) {
            const deviceObj = this.devices[index];
            deviceObj.manuallyDisconnect = true;
            // 尝试断开蓝牙连接
            try {
              if (
                deviceObj.device &&
                deviceObj.device.gatt &&
                deviceObj.device.gatt.connected
              ) {
                await deviceObj.device.gatt.disconnect();
              }
            } catch (disconnectErr) {
              console.warn("⚠️ 断开时出错：", disconnectErr);
            }

            // 移除设备
            this.devices.splice(index, 1);
          } else {
            this.$message.warning("未在设备列表中找到");
          }
        })
        .catch(() => {});
    },
    clearAll() {
      this.$confirm("即将为您移除所有设备", "提示", {
        confirmButtonText: "确定",
        showCancelButton: false,
        type: "warning",
        showClose: false,
      })
        .then(async () => {
          // 先尝试断开所有连接
          this.devices.forEach((d) => {
            d.manuallyDisconnect = true;
            try {
              if (d.device?.gatt?.connected) {
                d.device.gatt.disconnect();
                console.log(`🔌 已断开设备: ${d.device.name}`);
              }
            } catch (err) {
              console.warn(`❌ 断开设备出错: ${d.device.name}`, err);
            }
          });

          // 清空设备列表
          this.devices = [];
          // 可选：清除已保存的本地记录
          localStorage.removeItem("rememberedBluetoothDevices");

          this.$message.success("已清空所有设备");
        })
        .catch(() => {});
    },
    // 切换选中状态
    changeCheck(index) {
      // if (this.devices[index].passed) return;
      this.devices[index].isSeleted = !this.devices[index].isSeleted;
      this.devices.map((el) => {
        if (!el.isSeleted) {
          this.isAllCheck = false;
        }
      });
    },
    // 全选/全部选
    changeAllCheck() {
      this.isAllCheck = !this.isAllCheck;
      this.devices = this.devices.map((el) => {
        // if (!el.passed) {
        el.isSeleted = this.isAllCheck;
        // }
        return el;
      });
    },
    // 传感器校验
    sensorOpen() {
      const arr = this.devices.filter((el) => el.isSeleted && !el.passed);
      if (arr.length === 0) {
        this.$message.error("请选择需要校准的手柄");
        return;
      }
      this.selectNumber = arr.length;
      this.sensorVisible = true;
    },
    sensorClose() {
      this.sensorVisible = false;
      this.nowNumber = 0;
    },
    sumbitSensor() {
      let frame = [0xa5, 0x06, 0xbb, 0x55, 0x66];
      const checksum = frame.reduce((sum, val) => sum + val, 0) & 0xff;
      frame.push(checksum);
      const sendHex = frame
        .map((val) => val.toString(16).padStart(2, "0"))
        .join("");
      console.log("发送 Hex：", sendHex);
      const buffer = new Uint8Array(frame).buffer;
      this.devices.forEach((deviceObj, index) => {
        if (deviceObj.connected && deviceObj.isSeleted && !deviceObj.passed) {
          this.sendDataToDevice(deviceObj, buffer, "sensorClose");
        } else {
          console.warn(
            `❌ 设备 ${deviceObj.device.name || index} 未连接，跳过`
          );
        }
      });
    },
    // 摇杆校验
    rockerOpen() {
      const arr = this.devices.filter((el) => el.isSeleted && !el.passed);
      if (arr.length === 0) {
        this.$message.error("请选择需要校准的手柄");
        return;
      }
      this.selectNumber = arr.length;
      this.rockerVisible = true;
    },
    rockerClose() {
      this.rockerVisible = false;
      this.nowNumber = 0;
    },
    sumbitRocker() {
      let frame = [0xa5, 0x06, 0xbb, 0x77, 0x88];
      const checksum = frame.reduce((sum, val) => sum + val, 0) & 0xff;
      frame.push(checksum);
      const sendHex = frame
        .map((val) => val.toString(16).padStart(2, "0"))
        .join("");
      console.log("发送 Hex：", sendHex);
      const buffer = new Uint8Array(frame).buffer;
      this.devices.forEach((deviceObj, index) => {
        if (deviceObj.connected && deviceObj.isSeleted && !deviceObj.passed) {
          this.sendDataToDevice(deviceObj, buffer, "rockerClose");
        } else {
          console.warn(
            `❌ 设备 ${deviceObj.device.name || index} 未连接，跳过`
          );
        }
      });
    },
    // 马达
    motorOpen() {
      const arr = this.devices.filter((el) => el.isSeleted);
      if (arr.length === 0) {
        this.$message.error("请选择需要测试的手柄");
        return;
      }
      this.selectNumber = arr.length;
      // this.motorVisible = true;
      this.sumbitMotor();
    },
    motorClose() {
      this.motorVisible = false;
      this.nowNumber = 0;
    },
    sumbitMotor() {
      if (!this.isMotor) {
        this.power = 50;
        this.time = 1;
      } else {
        this.power = 50;
        this.time = 10;
      }
      let frame = [0xa5, 0x06, 0xb6, this.power, this.time];
      const checksum = frame.reduce((sum, val) => sum + val, 0) & 0xff;
      frame.push(checksum);
      const sendHex = frame
        .map((val) => val.toString(16).padStart(2, "0"))
        .join("");
      console.log("发送 Hex：", sendHex);
      this.isMotor = !this.isMotor;
      const buffer = new Uint8Array(frame).buffer;
      this.devices.forEach((deviceObj, index) => {
        if (deviceObj.connected && deviceObj.isSeleted) {
          this.sendDataToDevice(deviceObj, buffer, "motorClose");
        } else {
          console.warn(
            `❌ 设备 ${deviceObj.device.name || index} 未连接，跳过`
          );
        }
      });
    },
    // 写入信息
    async writeOpen() {
      const arr = this.devices.filter((el) => el.isSeleted);
      if (arr.length === 0) {
        this.$message.error("请选择需要写入信息的手柄");
        return;
      }
      this.selectNumber = arr.length;
      try {
        const frame = [0xa5, 0x06, 0xbb, 0x33, 0x44];
        const checksum = frame.reduce((sum, val) => sum + val, 0) & 0xff;
        frame.push(checksum);
        const buffer = new Uint8Array(frame).buffer;
        this.loading = true;
        this.loadingMessage = "设备信息读取中，请稍后...";
        // 所有并发发送
        await Promise.all(
          arr.map((deviceObj) => this.sendDataToDevice(deviceObj, buffer))
        );

        // 所有设备成功发送完后
        setTimeout(() => {
          this.writeVisible = true;
        }, 200);
        this.loading = false;
      } catch (err) {
        this.$message.error("部分设备发送失败，请重试");
      } finally {
        this.loading = false;
      }
    },
    writeClose() {
      this.writeVisible = false;
    },
    async sumbitWrite() {
      this.writeLoading = true;

      // 过滤出已连接且已选中的设备
      const targetDevices = this.devices.filter(
        (deviceObj) => deviceObj.connected && deviceObj.isSeleted
      );

      // 如果没有可发送的设备，提示并结束
      if (targetDevices.length === 0) {
        this.$message.warning("没有选中的已连接设备，无法写入！");
        this.writeLoading = false;
        return;
      }

      // 并发发送指令
      const promises = targetDevices.map((deviceObj) =>
        this.sendCommandQueueByStrList(deviceObj)
          .then(() =>
            console.log(`✅ 设备 ${deviceObj.device.name} 命令发送成功`)
          )
          .catch((err) =>
            console.error(`❌ 设备 ${deviceObj.device.name} 命令发送失败`, err)
          )
      );

      await Promise.all(promises);
      this.writeLoading = false;
      this.writeClose();
      this.$message.success("所有选中设备的指令已发送完成！");
    },
    async sendCommandQueueByStrList(deviceObj) {
      deviceObj.deviceMsg[5] = deviceObj.deviceMsg[5] + "_" + deviceObj.secret;
      const strList = Object.values(deviceObj.deviceMsg);
      for (let i = 0; i < strList.length; i++) {
        const code = i + 1;
        const str = strList[i];
        // console.log(code, str);
        const buffer = this.buildCustomCommand(code, str);

        try {
          await this.sendDataToDevice(deviceObj, buffer);
          await this.delay(20); // 每条命令之间间隔 20ms
        } catch (err) {
          console.error(
            `❌ 第${code}条命令发送失败，设备 ${deviceObj.device.name}`,
            err
          );
          throw err; // 中断本设备的发送
        }
      }
    },
    delay(ms) {
      return new Promise((resolve) => setTimeout(resolve, ms));
    },
    buildCustomCommand(code, asciiStr) {
      const HEADER = [0xa5]; // 固定头
      const TYPE = [0xb1]; // 命令类型
      const CMD = [parseInt(code) & 0xff]; // 命令编号（确保在 0-255）

      const strBytes = this.stringToBytes(asciiStr); // 字符串转字节

      const content = [...TYPE, ...CMD, ...strBytes]; // 拼接内容
      const len = [HEADER.length + 1 + content.length + 1]; // LEN = header + len字段 + 内容 + 校验

      const packetWithoutChecksum = [...HEADER, ...len, ...content];

      // 异或校验（XOR）
      const checksum = packetWithoutChecksum.reduce((acc, val) => acc ^ val, 0);

      const fullPacket = [...packetWithoutChecksum, checksum];

      return new Uint8Array(fullPacket).buffer;
    },
    stringToBytes(str) {
      const bytes = [];
      for (let i = 0; i < str.length; ++i) {
        bytes.push(str.charCodeAt(i) & 0xff); // 只保留低位字节
      }
      return bytes;
    },
    async sendDataToDevice(deviceObj, buffer, fn) {
      try {
        const service = await deviceObj.server.getPrimaryService(
          this.serviceUUID
        );
        const writeChar = await service.getCharacteristic(
          "0000ffe1-0000-1000-8000-00805f9b34fb"
        );
        await writeChar.writeValue(buffer);
        console.log(`✅ 向 ${deviceObj.device.name} 发送成功`);
        this.nowNumber++;
        if (this.nowNumber >= this.selectNumber && fn) {
          this[fn]();
        }
        return true; // 成功时返回
      } catch (err) {
        console.error("❌ 发送失败:", err);
        throw err;
      }
    },

    hexStringToBuffer(hex) {
      const bytes = new Uint8Array(hex.length / 2);
      for (let i = 0; i < hex.length; i += 2) {
        bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
      }
      return bytes.buffer;
    },
    // 上传合格设备
    async sumbitQualified() {
      if (!this.qualityTestingAccount) {
        this.$message.error("请输入质检员账号");
        return;
      }
      if (!this.qualityTestingCode) {
        this.$message.error("请输入质检码");
        return;
      }
      const passedList = this.devices.filter((el) => el.passed);
      if (passedList.length === 0) {
        this.$message.error("暂无检验到合格设备");
        return;
      }
      if (this.uploading) return; // 防止重复提交
      this.uploading = true;
      const snCodeList = passedList.map((el) => {
        return {
          deviceName: el.device.name,
          snCode: el.device.id,
        };
      });
      const params = {
        qualityTestingAccount: this.qualityTestingAccount,
        qualityTestingCode: this.qualityTestingCode,
        snCodeList,
      };
      // console.log(params);
      try {
        // 异步操作
        await addCheckDevice(params);
        this.$message.success("合格设备已上传成功！");
        this.getCount();
      } catch (error) {
        const match = error.match(/([A-Z]+\d+_[a-zA-Z0-9]+)/);

        if (match) {
          const msg = error.replace("Error: ", "");
          this.$confirm(msg + ",即将为您移除这个设备", "提示", {
            confirmButtonText: "确定",
            showCancelButton: false,
            type: "warning",
            showClose: false,
          })
            .then(async () => {
              const deviceName = match[1];
              console.log("提取到设备名:", deviceName);
              // 查找设备对象
              const index = this.devices.findIndex(
                (d) => d.device.name === deviceName
              );

              if (index !== -1) {
                const deviceObj = this.devices[index];
                deviceObj.manuallyDisconnect = true;
                // 尝试断开蓝牙连接
                try {
                  if (
                    deviceObj.device &&
                    deviceObj.device.gatt &&
                    deviceObj.device.gatt.connected
                  ) {
                    await deviceObj.device.gatt.disconnect();
                    console.log(`🔌 已断开蓝牙连接：${deviceName}`);
                  }
                } catch (disconnectErr) {
                  console.warn(`⚠️ 断开 ${deviceName} 时出错：`, disconnectErr);
                }

                // 移除设备
                this.devices.splice(index, 1);
                this.$message.warning(
                  `设备 ${deviceName} 已移除（重复质检或失败）`
                );
              } else {
                this.$message.warning(`未在设备列表中找到 ${deviceName}`);
              }
            })
            .catch(() => {});
        }
      } finally {
        this.uploading = false;
      }
    },
    // 获取质检员上传合格数量
    async getCount() {
      const params = {
        qualityTestingAccount: this.qualityTestingAccount,
        qualityTestingCode: this.qualityTestingCode,
        startTime: this.$moment().startOf("day").format("YYYY-MM-DD HH:mm:ss"),
        endTime: this.$moment().endOf("day").format("YYYY-MM-DD HH:mm:ss"),
      };
      const res = await getQualityCount(params);
      this.qualityCount = res.result;
    },
    // 保存质检员信息
    saveQualityTestingAccount(value) {
      localStorage.setItem("QualityTestingAccount", value);
    },
    saveQualityTestingCode(value) {
      localStorage.setItem("QualityTestingCode", value);
    },
  },
};
</script>

<style scoped lang="scss">
.bluetooth {
  padding: 20px;
  width: 100vw;
  height: 100vh;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  .overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    background: rgba(0, 0, 0, 0.4);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 999;
  }
  .passnumber {
    position: fixed;
    bottom: 6%;
    right: 1%;
    width: 100px;
    height: 100px;
    border-radius: 100%;
    // background: rgb(0, 159, 13);
    background: linear-gradient(135deg, #00c6ff, #0072ff);
    box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    z-index: 999;
    p {
      font-size: 20px;
      white-space: nowrap;
      line-height: 25px;
      color: #fff;
    }
  }
  .loading-box {
    background: #fff;
    padding: 20px 30px;
    border-radius: 10px;
    font-size: 16px;
    font-weight: bold;
  }
  .handle-box {
    // padding-bottom: 22px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    .btn-box {
      display: flex;
      align-items: center;
      button {
        padding: 10px 20px;
        font-size: 18px;
        border-radius: 10px;
        margin-right: 10px;
        color: #ffffff;
        background: #323232;
        border: none;
        cursor: pointer;
        font-weight: bold;
      }
    }
    ::v-deep .input-box {
      flex: 1;
      width: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      .el-input {
        width: 300px;
        font-size: 22px;
        margin-right: 20px;
        .el-input__inner {
          height: 52px;
          line-height: 52px;
          padding-left: 36px;
          box-sizing: border-box;
        }
        .el-input__inner:focus {
          border-color: #ff5d2f;
        }
        .el-input__icon {
          font-size: 26px;
          line-height: 50px;
        }
      }
    }
    .seleted {
      display: flex;
      // align-items: center;
      align-items: center;
      font-size: 24px;
      font-weight: bold;
      margin-right: 10px;
      p {
        width: 30px;
        height: 30px;
        margin-right: 10px;
        img {
          width: 100%;
          height: 100%;
        }
      }
    }
    .operate-box {
      display: flex;
      align-items: center;
    }
  }
  ::v-deep .el-divider {
    .el-divider__text {
      font-size: 18px;
    }
  }
  .device-block {
    display: flex;
    flex-wrap: wrap;
    ::v-deep .box-card {
      width: calc(12.5% - 10px);
      margin: 0 10px 30px 0;
      border-radius: 10px;
      position: relative;
      overflow: visible;
      border: 1px solid #323232;
      .el-divider {
        margin: 10px 0;
      }
      .el-card__header {
        padding: 0;
        .clearfix {
          padding: 8px;
          display: flex;
          align-items: center;
          justify-content: space-between;
          .title {
            display: flex;
            align-items: center;
            h2 {
              font-weight: bold;
              font-size: 24px;
              margin-right: 8px;
              white-space: nowrap;
            }
            p {
              font-weight: 500;
              font-size: 16px;
              white-space: nowrap;
            }
          }
        }
        .battery-box {
          display: flex;
          align-items: center;
          /* 电池外壳样式 */
          .battery-shell {
            width: 25px;
            height: 15px;
            border: 2px solid #959595;
            border-radius: 4px;
            background-color: #f9f9f9;
            position: relative;
            overflow: hidden;
            /* 电量填充条 */
            .battery-level {
              height: 100%;
              border-radius: 4px 0 0 4px;
              transition: width 0.4s ease;
            }
          }
          .battery-text {
            font-size: 16px;
            line-height: 1;
            font-weight: bold;
            margin-left: 4px;
          }
        }
      }
      .el-card__body {
        padding: 0 0 10px 0;
        .key-box {
          .top {
            display: flex;
            align-items: center;
            justify-content: space-around;
            box-sizing: border-box;
            padding: 5px 2px;
            > div {
              display: flex;
              align-items: center;
              flex-direction: column;
              font-weight: bold;
              font-size: 22px;
              color: #ffffff;
              line-height: 24px;
              width: 50px;
              border-radius: 4px;
            }
          }
          .center {
            display: flex;
            align-items: center;
            justify-content: space-around;
            padding: 10px 0 0;
            .joystick-container {
              display: flex;
              flex-direction: column;
              align-items: center;
              .joystick-base {
                width: 100px;
                height: 100px;
                background-color: #2c3e50;
                border-radius: 50%;
                position: relative;
                overflow: hidden;
                box-shadow: inset 0 0 10px #00000066;
              }
              .joystick-stick {
                width: 30px;
                height: 30px;
                background-color: #27ff2c;
                border-radius: 50%;
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(0, 0);
                transition: transform 0.2s ease;
                margin: -15px 0 0 -15px; /* 中心居中偏移 */
              }
            }
            .button {
              display: flex;
              flex-direction: column;
              align-items: center;
              .cicle-box {
                display: flex;
                align-items: center;
                .cicle-button:first-child {
                  margin-right: 8px;
                }
                .cicle-button:last-child {
                  margin-left: 8px;
                }
              }
              .cicle-button {
                width: 30px;
                height: 30px;
                border-radius: 50%;
                display: flex;
                align-items: center;
                justify-content: center;
                font-weight: bold;
                font-size: 22px;
                color: #ffffff;
                line-height: 24px;
              }
            }
          }
        }
        .gyroscope {
          display: flex;
          flex-direction: column;
          align-items: center;
          > div {
            margin-bottom: 10px;
          }
          > div:last-child {
            margin-bottom: 0;
          }
          /* 🔄 三个点闪烁动画 */
          .loading-dots {
            margin-bottom: 10px;
            display: flex;
            align-items: center;
            .dot {
              display: inline-block;
              width: 8px;
              height: 8px;
              margin: 0 4px;
              background-color: #ff7300;
              border-radius: 50%;
              animation: blink 1.4s infinite ease-in-out both;
            }
            .dot:nth-child(1) {
              animation-delay: -0.32s;
            }
            .dot:nth-child(2) {
              animation-delay: -0.16s;
            }
            p {
              font-size: 24px;
              font-weight: bold;
              margin-left: 10px;
              color: #dc143c;
            }
          }
          @keyframes blink {
            0%,
            80%,
            100% {
              opacity: 0;
              transform: scale(0.8);
            }
            40% {
              opacity: 1;
              transform: scale(1.2);
            }
          }

          /* ✅ 打勾出现动画 */
          .check-success,
          .check-fail {
            animation: fadeIn 0.8s ease forwards;
            display: flex;
            align-items: center;
            p {
              font-size: 24px;
              font-weight: bold;
              margin-left: 10px;
              color: #27a72b;
            }
            .check-icon {
              font-size: 32px;
              color: #4caf50;
              font-weight: bold;
              animation: scaleIn 0.4s ease;
            }
            @keyframes fadeIn {
              from {
                opacity: 0;
                transform: translateY(10px);
              }
              to {
                opacity: 1;
                transform: translateY(0);
              }
            }
            @keyframes scaleIn {
              from {
                transform: scale(0);
                opacity: 0;
              }
              to {
                transform: scale(1);
                opacity: 1;
              }
            }
          }
          .check-fail {
            p {
              color: #fc2424;
            }
            .check-icon {
              color: #f92a2a;
            }
          }
        }
        .button-box {
          display: flex;
          align-items: center;
          justify-content: space-around;
          p {
            display: flex;
            align-items: center;
            font-size: 18px;
            font-weight: bold;
            border: 1px solid #86381b;
            border-radius: 6px;
            padding: 4px 10px;
            img {
              width: 26px;
              height: 26px;
              margin-right: 4px;
            }
            .el-icon-remove {
              font-size: 24px;
              color: red;
              margin-right: 4px;
            }
            .el-icon-cpu {
              font-size: 24px;
              color: #454545;
              margin-right: 4px;
            }
          }
        }
      }

      .calibration-box {
        display: flex;
        align-items: center;
        justify-content: space-around;
      }
      .calibration {
        display: flex;
        h2 {
          font-weight: bold;
          font-size: 20px;
        }
        div {
          display: flex;
          align-items: center;
          p {
            width: 22px;
            height: 22px;
            margin: 0 8px;
            img {
              width: 100%;
              height: 100%;
            }
          }
          span {
            font-size: 20px;
          }
        }
      }
    }

    .box-card:hover {
      transform: translateY(-4px) scale(1.03);
      box-shadow: 0 0 30px rgba(26, 40, 40, 0.4);
    }
    .blink-error {
      animation: softBlink 3s infinite ease-in-out;
      transition: background-color 0.5s ease;
    }
    @keyframes softBlink {
      0% {
        background-color: rgba(250, 250, 250, 0.1);
      }
      50% {
        background-color: rgb(249, 72, 72, 0.5);
      }
      100% {
        background-color: rgba(250, 250, 250, 0.1);
      }
    }
    .device-success {
      background-color: rgba(99, 255, 177, 0.4);
    }
  }
  .sensor-box {
    text-align: center;
    font-weight: bold;
    font-size: 24px;
    color: #323232;
  }
  .rocker-box {
    p {
      line-height: 36px;
      font-weight: bold;
      font-size: 22px;
      color: #323232;
    }
  }
  ::v-deep .motor-box {
    > div:first-child {
      margin-bottom: 30px;
    }
    > div {
      > span {
        display: block;
        font-size: 20px;
        font-weight: 600;
        margin-bottom: 16px;
      }
      > div {
        display: flex;
        align-items: center;
        > div {
          flex: 1;
          width: 0;
          .el-slider__bar {
            background: #ff5d2f;
          }
          .el-slider__button {
            border: 2px solid #ff5d2f;
            background: #ff5d2f;
            width: 20px;
            height: 20px;
          }
        }
        > p {
          font-size: 20px;
          font-weight: bold;
          color: #333333;
          margin-left: 20px;
        }
      }
    }
  }
}
@keyframes gradientMove {
  0% {
    background-position: 0% 50%;
  }
  50% {
    background-position: 100% 50%;
  }
  100% {
    background-position: 0% 50%;
  }
}
</style>
